package com.test.mapper.dao.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.RowMapper;

import com.test.dao.AbstractDao;
import com.test.dao.DBOperationResult;
import com.test.mapper.dao.EntityDao;
import com.test.mapper.enitites.EntityModel;
import com.test.mapper.enitites.EntityProperty;
import com.test.mapper.enitites.pk.EntityModelPK;

public class EntityDaoImpl extends AbstractDao implements EntityDao {

	private final Map<String, String> propertyNameToDBColumnMap;

	private static final String TABLE_NAME = "entities";

	private static final String SELECT =
		"select client, table_name, name, primary_key_column"
		+ " from " + TABLE_NAME;

	private static final String ADD =
		"insert into " + TABLE_NAME
		+ " (client, table_name, name, primary_key_column)"
		+ " values (?, ?, ?, ?)";

	private static final String UPDATE =
		"update " + TABLE_NAME
		+ " set name = ?, primary_key_column = ?"
		+ " where client = ? and table_name = ?";

	private static final String REMOVE =
		" delete from " + TABLE_NAME
		+ " where client = ? and table_name = ?";

	public EntityDaoImpl() {
		propertyNameToDBColumnMap = new HashMap<String, String>();
		propertyNameToDBColumnMap.put("clientId", "client");
		propertyNameToDBColumnMap.put("entityName", "name");
		propertyNameToDBColumnMap.put("tableName", "table_name");
		propertyNameToDBColumnMap.put("primaryKey.columnName", "primary_key_column");
	}

	@Override
	protected String mapPropertyNameToDBColumn(final String propertyName) {
		return propertyNameToDBColumnMap.get(propertyName);
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.dao.Dao#add(java.lang.Object)
	 */
	public DBOperationResult add(final EntityModel newObject) {

		logger.debug("START add");

		try {
			final int rows = getSimpleJdbcTemplate().update(ADD,
					newObject.getClientId(), newObject.getTableName(), newObject.getEntityName(),
					newObject.getPrimaryKey().getColumnName());
			logger.debug("Number of rows affected: " + rows);

			return getResultForAffectedRows(rows);
		} catch (DataAccessException dae) {
			logger.error("SQL error adding entity", dae);
			return getResultForDataAccessException(dae);
		} catch (Exception e) {
			logger.error("Generic error adding entity", e);
			return DBOperationResult.ERROR_UNKNOWN;
		} finally {
			logger.debug("END add");
		}
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.dao.Dao#update(java.lang.Object)
	 */
	public DBOperationResult update(final EntityModel object) {

		logger.debug("START update");
		logger.debug("Client ID: " + object.getClientId());
		logger.debug("Table name: " + object.getTableName());

		try {
			final int rows = getSimpleJdbcTemplate().update(UPDATE,
					object.getEntityName(), object.getPrimaryKey().getColumnName(),
					object.getClientId(), object.getTableName());
			logger.debug("Affected rows: " + rows);

			return getResultForAffectedRows(rows);
		} catch (DataAccessException dae) {
			logger.error("SQL error updating entity", dae);
			return getResultForDataAccessException(dae);
		} catch (Exception e) {
			logger.error("Generic error updating entity", e);
			return DBOperationResult.ERROR_UNKNOWN;
		} finally {
			logger.debug("END update");
		}
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.dao.Dao#remove(java.lang.Object)
	 */
	public DBOperationResult remove(final EntityModel object) {

		logger.debug("START remove");
		logger.debug("Client ID: " + object.getClientId());
		logger.debug("Table name: " + object.getTableName());

		try {
			final int rows = getSimpleJdbcTemplate().update(REMOVE,
					object.getClientId(), object.getTableName());
			logger.debug("Affected rows: " + rows);

			return getResultForAffectedRows(rows);
		} catch (DataAccessException dae) {
			logger.error("SQL error removing entity", dae);
			return getResultForDataAccessException(dae);
		} catch (Exception e) {
			logger.error("Generic error removing entity", e);
			return DBOperationResult.ERROR_UNKNOWN;
		} finally {
			logger.debug("END remove");
		}
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.dao.Dao#search(java.util.Map)
	 */
	public Collection<EntityModel> search(final Map<String, Collection<? extends Object>> propertyCriteriaMap) {

		logger.debug("START search");

		final String whereCondition = buildWhereCondition(propertyCriteriaMap);
		final String sql = StringUtils.isEmpty(whereCondition) ? SELECT : SELECT + " where " + whereCondition;

		logger.debug("SQL: " + sql);

		List<EntityModel> result = null;
		try {
			result = getSimpleJdbcTemplate().query(sql, new EntityModelMapper());
		} catch (DataAccessException dae) {
			logger.error("SQL exception while searching for entities", dae);
		} catch (Exception e) {
			logger.error("Generic error while searching for entities", e);
		} finally {
			logger.debug("Result size: " + result == null ? "null" : result.size());
			logger.debug("END search");
		}

		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.dao.Dao#load(java.lang.Object)
	 */
	public EntityModel load(final EntityModelPK pk) {

		logger.debug("START load");
		logger.debug("Primary key: " + pk);

		final String sql = SELECT + " where client = ? and table_name = ?";
		try {
			final List<EntityModel> entities = getSimpleJdbcTemplate().query(sql,
					new EntityModelMapper(), pk.getClientId(), pk.getTableName());
			if (entities.size() == 0) {
				return null;
			} else {
				if (entities.size() > 1) {
					logger.warn("Multiple entities found for PK " + pk);
				}
				return entities.get(0);
			}
		} catch (DataAccessException dae) {
			logger.error("SQL exception when loading entity with PK " + pk, dae);
			return null;
		} catch (Exception e) {
			logger.error("Generic error loading entity with PK " + pk, e);
			return null;
		} finally {
			logger.debug("END load");
		}
	}

	/**
	 * ResultSet row mapper for the EntityModel.
	 */
	private class EntityModelMapper implements RowMapper<EntityModel> {

		public EntityModel mapRow(final ResultSet rs, final int rowNum) throws SQLException {
			final EntityModel model = new EntityModel();
			model.setClientId(rs.getInt("client"));
			model.setTableName(rs.getString("table_name"));
			model.setEntityName(rs.getString("name"));
			final EntityProperty property = new EntityProperty();
			property.setColumnName(rs.getString("primary_key_column"));
			model.setPrimaryKey(property);
			return model;
		}
		
	}
}
